package io.sundial.job.impl;

import io.sundial.job.*;
import io.sundial.job.bind.JobData;
import io.sundial.job.bind.JobField;
import io.sundial.job.bind.JobResolver;
import io.sundial.job.bind.JobResolvingException;

import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.WeakHashMap;

/**
 * 参数化作业
 * 通过反射当前作业类的属性来
 *
 * @author Payne 646742615@qq.com
 * 2019/1/6 17:17
 */
public abstract class ParameterizedJob extends DeclaredJob {
    private final static Map<ClassLoader, JobResolver> CACHE = new WeakHashMap<>();

    @Override
    public Map<String, JobParameter> parameters() {
        try {
            JobResolver jobResolver = getJobResolver();
            Map<String, JobParameter> parameters = new LinkedHashMap<>();
            Class<? extends Job> jobClass = this.getClass();
            PropertyDescriptor[] properties = Introspector.getBeanInfo(jobClass).getPropertyDescriptors();
            for (PropertyDescriptor property : properties) {
                String name = property.getName();
                if (name.equals("class")) continue;

                Method getter = property.getReadMethod();
                Method setter = property.getWriteMethod();
                if (getter == null || setter == null) continue;

                Class<?> type = property.getPropertyType();
                Object value = getter.invoke(this);
                JobField field = new JobField(jobClass, type, name, value, getter, setter);
                JobParameter parameter = jobResolver.resolve(field);
                parameters.put(name, parameter);
            }
            return parameters;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    @Override
    public JobResult run(JobContext jobContext) throws JobException {
        try {
            JobResolver jobResolver = getJobResolver();
            Class<? extends ParameterizedJob> jobClass = this.getClass();
            ParameterizedJob job = jobClass.newInstance();
            Map<String, String> arguments = jobContext.getJobArguments();
            JobData data = new JobData(arguments);
            PropertyDescriptor[] properties = Introspector.getBeanInfo(jobClass).getPropertyDescriptors();
            for (PropertyDescriptor property : properties) {
                String name = property.getName();
                if (name.equals("class")) continue;

                Method getter = property.getReadMethod();
                Method setter = property.getWriteMethod();
                if (getter == null || setter == null) continue;

                Class<?> type = property.getPropertyType();
                Object value = getter.invoke(this);
                JobField field = new JobField(jobClass, type, name, value, getter, setter);
                Object argument = jobResolver.resolve(field, data, jobContext);
                if (argument != JobResolver.DEFAULT) setter.invoke(job, argument);
            }
            return job.execute(jobContext);
        } catch (Exception e) {
            throw new JobException(e);
        }
    }

    /**
     * 执行作业
     *
     * @param jobContext 作业执行参数
     * @return 作业执行结果
     */
    protected abstract JobResult execute(JobContext jobContext) throws JobException;

    private JobResolver getJobResolver() {
        ClassLoader classLoader = this.getClass().getClassLoader();
        JobResolver jobResolver = CACHE.get(classLoader);
        if (jobResolver != null) {
            return jobResolver;
        }
        synchronized (CACHE) {
            jobResolver = CACHE.get(classLoader);
            if (jobResolver != null) {
                return jobResolver;
            }
            CACHE.put(classLoader, jobResolver = new SPIJobResolver(classLoader));
        }
        return jobResolver;
    }

    private static class SPIJobResolver implements JobResolver {
        private final ServiceLoader<JobResolver> jobResolvers;

        SPIJobResolver(ClassLoader classLoader) {
            this.jobResolvers = ServiceLoader.load(JobResolver.class, classLoader);
        }

        @Override
        public boolean supports(JobField field) {
            for (JobResolver jobResolver : jobResolvers) {
                if (jobResolver.supports(field)) {
                    return true;
                }
            }
            return false;
        }

        @Override
        public JobParameter resolve(JobField field) throws JobResolvingException {
            for (JobResolver jobResolver : jobResolvers) {
                if (jobResolver.supports(field)) {
                    return jobResolver.resolve(field);
                }
            }
            throw new JobResolvingException();
        }

        @Override
        public Object resolve(JobField field, JobData data, JobContext context) throws Exception {
            for (JobResolver jobResolver : jobResolvers) {
                if (jobResolver.supports(field)) {
                    return jobResolver.resolve(field, data, context);
                }
            }
            throw new JobResolvingException();
        }

    }
}
